package com.atguigu.gmall.index.config;

import org.apache.commons.lang.ArrayUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RBloomFilter;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.Random;
import java.util.concurrent.TimeUnit;

/*
缓存管理的切面类
 */
@Component
@Aspect
public class GmallCacheAspect {
    @Autowired
    RedisTemplate redisTemplate;
    @Autowired
    RedissonClient redissonClient;
//    @Before() 前置通知
//    @After() 后置通知
//    @AfterReturning 返回通知
//    @AfterThrowing 异常通知
    //环绕通知：使用缓存管理注解的方法
//    @Around("execution()")  execution使用 方法的全路径(包名.类名.方法名) 去管理一个方法
    @Around("@annotation(com.atguigu.gmall.index.config.GmallCache)")//@annotation使用注解的全类名 去查找管理使用该注解的方法
    public Object cacheManage(ProceedingJoinPoint joinPoint){
        RLock lock = null;
        try {
            Object[] args = joinPoint.getArgs();//获取目标方法的参数列表
            //获取目标方法上缓存管理注解的参数值：
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            Method method = signature.getMethod(); //获取目标方法对象
            String name = method.getName();//目标方法名
            GmallCache gmallCache = method.getAnnotation(GmallCache.class);//获取目标方法上的注解对象
            String cacheKeyPrefix = gmallCache.cacheKeyPrefix();
            String lockKeyPrefix = gmallCache.lockKeyPrefix();
            int random = gmallCache.random();
            int timeout = gmallCache.timeout();
            int nullTimtout = gmallCache.nullTimtout();
            //key的拼接：保证每个缓存的键不一样
            String cacheKey = cacheKeyPrefix;
            String lockKey = lockKeyPrefix;
            if(ArrayUtils.isNotEmpty(args)){//如果实参列表不为空，使用实参值来拼接key
                // cache:cates:queryCatesAndsubs:1
                cacheKey+=name+":"+args[0].toString();
                lockKey+=name+":"+args[0].toString();
                //使用bloomfilter判数据是否存在
                RBloomFilter<Long> bloomFilter = redissonClient.getBloomFilter("cache:cates:bf");
                boolean contains = bloomFilter.contains(Long.parseLong(args[0].toString()));
                if(!contains){
                    //bf判定数据不存在  ，返回空
                    System.out.println("bloomfilter判断数据不存在");
                    return null;
                }

            }
            //1、检查是否有缓存：有缓存返回缓存
            Object cache = redisTemplate.opsForValue().get(cacheKey);
            if(cache!=null){
                //有缓存
                return cache;
            }
            //2、没有缓存执行目标的方法：查询数据
            //2.1 添加分布式锁:防止缓存雪崩
            lock = redissonClient.getLock(lockKey);
            lock.lock();
            //再次尝试获取缓存
            cache = redisTemplate.opsForValue().get(cacheKey);
            if(cache!=null){
                //有缓存
                return cache;
            }

            Object result = joinPoint.proceed(args);//执行目标方法
            //3、设置到缓存中
            redisTemplate.opsForValue().set(cacheKey , result , timeout+new Random().nextInt(random)
                    , TimeUnit.HOURS);
            // 返回结果
            return result;
        } catch (Throwable throwable) {
            throwable.printStackTrace();
            return null;
        } finally {
            //释放锁
            if(lock!=null){
                lock.unlock();
            }
        }
    }
}
